// HSS.Logging.Log.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       Log.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Logging
{
	#region Using Directives
	using System;
	using System.ComponentModel;
	using System.Diagnostics;
	using System.IO;
	using System.ServiceModel;
	using HSS.IO;
	#endregion

	#region Log
	/// <summary>
	/// HSS Log Client provides interaction with the HSS Logging Service
	/// </summary>
	[Description("HSS Log Client provides interaction with the HSS Logging Service")]
	[System.Drawing.ToolboxBitmap(typeof(HSS.Logging.Log), "HSS.Logging.Log")]
	public sealed partial class Log : Component, ILog
	{
		#region Constants
		/// <summary>
		/// The Public Schema for the HSS Log Event entries (http://schemas.highspeed-solutions.net/core/2006/09/events/event).
		/// </summary>
		public const string HSSLoggingSchema = "http://schemas.highspeed-solutions.net/core/2010/07/events/event";
		/// <summary>
		/// The URI to the HSS Log Service LogServer (net.pipe://hss/logservice/4000/logserver).
		/// </summary>
		public const string HSSLogServerURI = "net.pipe://hss/logservice/4000/logserver";
		#endregion

		#region Fields
		NetNamedPipeBinding binding;
		EndpointAddress epa;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs the Log using the HSSLogServerURI.
		/// </summary>
		public Log()
			: this(null)
		{
		}
		/// <summary>
		/// Constructs the Log using the HSSLogServerURI.
		/// </summary>
		/// <param name="container">The parent container.</param>
		public Log(IContainer container)
		{
			if (null != container)
				container.Add(this);
			this.InitializeComponent();
			this.LogServerURI = Log.HSSLogServerURI;
			this.binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
			this.epa = new EndpointAddress(Log.HSSLogServerURI);
		}
		static Log()
		{
			DefaultUserName = Environment.UserName;
			DefaultApplicationName = "HSS.Logging.Log";
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets the URI to the LogServer.
		/// </summary>
		[DesignOnly(true)]
		[Category("Behavior")]
		[Description("Gets the URI to the LogServer.")]
		public string LogServerURI
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets or sets the default username to associate with a log entry
		/// </summary>
		public static string DefaultUserName
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets the default name of the application associated with all new event entries.
		/// </summary>
		public static string DefaultApplicationName
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets the LogEntry formatter..
		/// </summary>
		public static ILogEntryFormatter Formatter
		{
			get;
			set;
		}
		#endregion

		#region Methods

		#region Write By Event-Type

		#region Warning
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="category">The category of the program that is writing this message.</param>
		public int WarningEvent(string message, string category = "Uncategorized")
		{
			return this.WriteEvent(message, GetDefaultUser(), category, string.Empty, DefaultApplicationName, string.Empty, EventLogEntryType.Warning);
		}
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="attachmentString">The Xml String to attach to this entry.</param>
		/// <param name="applicationName">The name of the application this event is associated with.</param>
		/// <param name="referenceNumber">A reference number to associate with the entry.</param>
		public int WarningEvent(string message, string userName, string category, string attachmentString = "", string applicationName = "", string referenceNumber = "")
		{
			return this.WriteEvent(message, userName, category, attachmentString, applicationName, referenceNumber, EventLogEntryType.Warning);
		}
		#endregion

		#region Error
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="category">The category of this message.</param>
		public int ErrorEvent(string message, string category = "Uncategorized")
		{
			return this.WriteEvent(message, GetDefaultUser(), category, string.Empty, DefaultApplicationName, string.Empty, EventLogEntryType.Error);
		}
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="attachmentString">The Xml String to attach to this entry.</param>
		/// <param name="applicationName">The name of the application this event is associated with.</param>
		/// <param name="referenceNumber">A reference number to associate with the entry.</param>
		public int ErrorEvent(string message, string userName, string category, string attachmentString = "", string applicationName = "", string referenceNumber = "")
		{
			return this.WriteEvent(message, userName, category, attachmentString, applicationName, referenceNumber, EventLogEntryType.Error);
		}
		#endregion

		#region Information
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="category">The category of this message.</param>
		public int InformationEvent(string message, string category = "Uncategorized")
		{
			return this.WriteEvent(message, GetDefaultUser(), category, string.Empty, DefaultApplicationName, string.Empty, EventLogEntryType.Information);
		}
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="attachmentString">The Xml String to attach to this entry.</param>
		/// <param name="applicationName">The name of the application this event is associated with.</param>
		/// <param name="referenceNumber">A reference number to associate with the entry.</param>
		public int InformationEvent(string message, string userName, string category, string attachmentString = "", string applicationName = "", string referenceNumber = "")
		{
			return this.WriteEvent(message, userName, category, attachmentString, applicationName, referenceNumber, EventLogEntryType.Information);
		}
		#endregion

		#region Failure Audit
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="category">The category of this message.</param>
		/// <param name="message">The message to write.</param>
		public int FailureAudit(string message, string category = "Uncategorized")
		{
			return this.WriteEvent(message, GetDefaultUser(), category, string.Empty, DefaultApplicationName, string.Empty, EventLogEntryType.FailureAudit);
		}
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="attachmentString">The Xml String to attach to this entry.</param>
		/// <param name="applicationName">The name of the application this event is associated with.</param>
		/// <param name="referenceNumber">A reference number to associate with the entry.</param>
		public int FailureAudit(string message, string userName, string category, string attachmentString = "", string applicationName = "", string referenceNumber = "")
		{
			return this.WriteEvent(message, userName, category, attachmentString, applicationName, referenceNumber, EventLogEntryType.FailureAudit);
		}
		#endregion

		#region Success Audit
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="category">The category of this message.</param>
		public int SuccessAudit(string message, string category = "Uncategorized")
		{
			return this.WriteEvent(message, GetDefaultUser(), category, string.Empty, DefaultApplicationName, string.Empty, EventLogEntryType.SuccessAudit);
		}
		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="attachmentString">The Xml String to attach to this entry.</param>
		/// <param name="applicationName">The name of the application this event is associated with.</param>
		/// <param name="referenceNumber">A reference number to associate with the entry.</param>
		public int SuccessAudit(string message, string userName, string category, string attachmentString = "", string applicationName = "", string referenceNumber = "")
		{
			return this.WriteEvent(message, userName, category, attachmentString, applicationName, referenceNumber, EventLogEntryType.SuccessAudit);
		}
		#endregion

		#region Exception
		/// <summary>
		/// Write an exception entry to a Log
		/// </summary>
		/// <param name="exception">The originating exception to write.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="xmlString">The Xml String to attach to this entry.</param>
		/// <param name="applicationName">The name of the application this exception is associated with.</param>
		/// <param name="referenceNumber">A reference number to associate with the entry.</param>
		/// <param name="eventType">The <see cref="EventLogEntryType"/> for the entry.</param>
		public int WriteException(Exception exception, string userName, string category, string xmlString, string applicationName, string referenceNumber, EventLogEntryType eventType)
		{
			LogEntry entry = new LogEntry(userName, category, xmlString, applicationName, referenceNumber, DateTime.Now, eventType);
			Log.GetFormatter().FormatException(exception, entry, this);
			return this.WriteEntryInternal(entry);
		}
		#endregion

		#endregion

		#region Static Writers

		/// <summary>
		/// Writes an Error Entry to the Machines Event Log
		/// </summary>
		/// <param name="message">The message to write</param>
		/// <param name="source">One of the <see cref="MachineEventLogSource"/> options</param>
		public static void WriteToMachineEventLog(string message, MachineEventLogSource source)
		{
			WriteToMachineEventLog(message, source, EventLogEntryType.Information);
		}
		/// <summary>
		/// Write an Entry to the Machines Event Log
		/// </summary>
		/// <param name="message">The message to write</param>
		/// <param name="source">One of the <see cref="MachineEventLogSource"/> options</param>
		/// <param name="eventType">The event type for the entry</param>
		public static void WriteToMachineEventLog(string message, MachineEventLogSource source, EventLogEntryType eventType)
		{
			if (string.IsNullOrEmpty(message))
				return;

			try
			{
				string version = typeof(Log).Assembly.GetName().Version.ToString(4);
				string log = "HSS " + version;
				string logSource = source.ToString() + " " + version;
				if (message.Length > 32766)
					message = message.Substring(0, 32766);
				using (EventLog ev = new EventLog(log, Environment.MachineName, logSource))
					ev.WriteEntry(message, eventType);
			}
			catch
			{
				Console.WriteLine(message);
			}
		}

		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="message">The message to write</param>
		/// <param name="eventType">The event type for the entry.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="applicationName">The name of the application this entry is associated with.</param>
		/// <param name="xmlString">The Xml String to attach to this entry.</param>
		/// <param name="referenceNumber">The reference number associated with this entry.</param>
		/// <exception cref="InvalidEnumArgumentException">EventType is not a valid <see cref="System.Diagnostics.EventLogEntryType"></see>.</exception>
		/// <exception cref="ArgumentNullException">Missing the Message to write</exception>
		/// <exception cref="ArgumentNullException">Missing the User in which the message is from</exception>
		/// <exception cref="ArgumentNullException">Missing the Category the message is for</exception>
		public static int WriteEntry(string message, EventLogEntryType eventType, string userName = "", string category = "Uncategorized", string applicationName = "", string xmlString = "", string referenceNumber = "")
		{
			int entryID = 0;

			try
			{
				if (string.IsNullOrEmpty(userName))
					userName = GetDefaultUser();
				if (string.IsNullOrEmpty(applicationName))
					applicationName = DefaultApplicationName;
				using (Log logInstance = new Log())
					entryID = logInstance.WriteEvent(message, userName, category, xmlString, applicationName, referenceNumber, eventType);
			}
			catch (Exception ex)
			{
				string msg = string.Format(HSS.Logging.Strings.ExceptionLoggingMessage, message, ex.ToString());
				Log.WriteToMachineEventLog(msg, MachineEventLogSource.HSSLogService, EventLogEntryType.Error);
				entryID = -1;
			}

			return entryID;
		}

		/// <summary>
		/// Write an Exception to the Log.
		/// </summary>
		/// <param name="exception">The exception to write.</param>
		/// <param name="userName">The name of the user to associate with this entry.</param>
		/// <param name="category">The category of this entry.</param>
		/// <param name="applicationName">The name of the application to associate with this entry.</param>
		/// <param name="xmlString">An xml string to record with this entry.</param>
		/// <param name="referenceNumber">The reference number to associate with this entry.</param>
		/// <returns>The id of the new event log entry.</returns>
		/// <exception cref="ArgumentNullException">Missing the Exception to write.</exception>
		public static int WriteException(Exception exception, string userName = "", string category = "Uncategorized", string applicationName = "", string xmlString = "", string referenceNumber = "")
		{
			if (null == exception)
				throw new ArgumentNullException("exception");

			int entryID = 0;

			try
			{
				if (string.IsNullOrEmpty(userName))
					userName = GetDefaultUser();
				if (string.IsNullOrEmpty(applicationName))
					applicationName = DefaultApplicationName;
				using (Log logInstance = new Log())
					entryID = logInstance.WriteException(exception, userName, category, xmlString, applicationName, referenceNumber, EventLogEntryType.Error);
			}
			catch
			{
				string msg = string.Format(HSS.Logging.Strings.ExceptionLoggingMessage, exception.ToString(), exception.Message);
				Log.WriteToMachineEventLog(msg, MachineEventLogSource.HSSLogService, EventLogEntryType.Error);
				entryID = -1;
			}

			return entryID;
		}
		/// <summary>
		/// Write an informational entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		public static int WriteInformation(string message)
		{
			return WriteEntry(message, EventLogEntryType.Information);
		}
		/// <summary>
		/// Write a success entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		public static int WriteSuccess(string message)
		{
			return WriteEntry(message, EventLogEntryType.SuccessAudit);
		}
		/// <summary>
		/// Write a failure entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		public static int WriteFailure(string message)
		{
			return WriteEntry(message, EventLogEntryType.FailureAudit);
		}
		/// <summary>
		/// Write a warning entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		public static int WriteWarning(string message)
		{
			return WriteEntry(message, EventLogEntryType.Warning);
		}
		/// <summary>
		/// Write an error entry to a Log
		/// </summary>
		/// <param name="message">The message to write.</param>
		public static int WriteError(string message)
		{
			return WriteEntry(message, EventLogEntryType.Error);
		}

		#endregion

		/// <summary>
		/// A method a client object can call to validate object existence
		/// </summary>
		/// <returns>true</returns>
		public bool Ping()
		{
			using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
				return channel.Ping();
		}

		private static string GetDefaultUser()
		{
			if (string.IsNullOrEmpty(Log.DefaultUserName))
				Log.DefaultUserName = Environment.UserName;
			return Log.DefaultUserName;
		}
		private static ILogEntryFormatter GetFormatter()
		{
			if (null == Log.Formatter)
				Log.Formatter = new DefaultFormatter();
			return Log.Formatter;
		}
		#endregion

		#region ILog Members

		/// <summary>
		/// Gets the LogType of the Logging Service
		/// </summary>
		[Browsable(false)]
		public EventLogType LogType
		{
			get
			{
				using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
					return channel.GetLogType();
			}
		}

		/// <summary>
		/// Save an attachment to the HSSLog Database
		/// </summary>
		/// <param name="entryId">The Id of the pre-existing Log Entry to attach.</param>
		/// <param name="content">The Content of the Attachment.</param>
		/// <param name="fileName">The File Name associated with this attachment. Uses the extension of the file to determine the Content-Type.</param>
		/// <returns>The AttachmentID</returns>
		/// <exception cref="InvalidOperationException">You can only add attachments when the LogType is to Database.</exception>
		public int AttachFile(int entryId, byte[] content, string fileName)
		{
			if (this.LogType == EventLogType.Machine)
				throw new InvalidOperationException(Strings.ExceptionLogging_AttachmentDatabaseOnly);

			string name = Path.GetFileNameWithoutExtension(fileName);
			string ext = Path.GetExtension(fileName);
			string ctt = MimeMapping.FromFileName(fileName);

			return AttachContent(entryId, content, ctt, name, ext);
		}

		/// <summary>
		/// Save an attachment to the HSSLog Database
		/// </summary>
		/// <param name="entryId">The Id of the pre-existing Log Entry to attach.</param>
		/// <param name="content">The Content of the Attachment.</param>
		/// <param name="contentType">The Mime Content-Type of the Attachment.</param>
		/// <param name="fileNameWithOutExtention">The File Name associated with this attachment with out the extension.</param>
		/// <param name="extensionWithDot">The File Extension associated with this attachment (.ext).</param>
		/// <returns>The AttachmentID</returns>
		/// <exception cref="InvalidOperationException">You can only add attachments when the LogType is to Database.</exception>
		public int AttachContent(int entryId, byte[] content, string contentType, string fileNameWithOutExtention, string extensionWithDot)
		{
			if (this.LogType == EventLogType.Machine)
				throw new InvalidOperationException(Strings.ExceptionLogging_AttachmentDatabaseOnly);

			using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
				return channel.AttachContent(entryId, content, contentType, fileNameWithOutExtention, extensionWithDot);
		}

		/// <summary>
		/// Truncate the Log, retaining the requested number of days
		/// </summary>
		/// <param name="daysToRetain">The number of days to retain</param>
		public void TruncateLog(int daysToRetain)
		{
			using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
				channel.TruncateLog(daysToRetain);
		}

		/// <summary>
		/// Write an event message to the Log.
		/// </summary>
		/// <param name="message">The message to write.</param>
		/// <param name="userName">The name of the person or program writing this message.</param>
		/// <param name="category">The category of this message.</param>
		/// <param name="xmlString">The Xml String to attach to this entry.</param>
		/// <param name="applicationName">The name of the application this entry is associated with.</param>
		/// <param name="referenceNumber">A reference number to associate with the entry.</param>
		/// <param name="eventType">The event type for the entry.</param>
		/// <exception cref="InvalidEnumArgumentException">EventType is not a valid <see cref="System.Diagnostics.EventLogEntryType"></see>.</exception>
		/// <exception cref="ArgumentNullException">Missing the Message to write</exception>
		/// <exception cref="ArgumentNullException">Missing the User in which the message is from</exception>
		/// <exception cref="ArgumentNullException">Missing the Category the message is for</exception>
		public int WriteEvent(string message, string userName, string category, string xmlString, string applicationName, string referenceNumber, EventLogEntryType eventType)
		{
			LogEntry entry = new LogEntry(message, userName, category, xmlString, applicationName, referenceNumber, DateTime.Now, eventType);
			Log.GetFormatter().Format(entry, this);
			return this.WriteEntryInternal(entry);
		}

		/// <summary>
		/// Write an entry to a Log
		/// </summary>
		/// <param name="entry">The Log Entry to write</param>
		/// <exception cref="ArgumentNullException">Missing the Entry to write</exception>
		/// <exception cref="ArgumentNullException">Missing the Message to write</exception>
		/// <exception cref="ArgumentNullException">Missing the User in which the message is from</exception>
		/// <exception cref="ArgumentNullException">Missing the Category the message is for</exception>
		/// <returns>The id of the new entry</returns>
		public int WriteEntry(LogEntry entry)
		{
			Log.GetFormatter().Format(entry, this);
			return WriteEntryInternal(entry);
		}

		int WriteEntryInternal(LogEntry entry)
		{
			#region Validate

			if (null == entry)
				throw new ArgumentNullException("Entry", "Missing the Entry to write");

			if (string.IsNullOrEmpty(entry.Message))
				throw new ArgumentNullException("Message", "Entry is missing the Message to write");

			if (string.IsNullOrEmpty(entry.User))
				throw new ArgumentNullException("User", "Entry is missing the User in which the message is from");

			if (string.IsNullOrEmpty(entry.Category))
				throw new ArgumentNullException("Category", "Entry is missing the Category of this message");

			#endregion

			int eventId = -1;

			try
			{
				using (ILogServerChannel channel = ChannelFactory<ILogServerChannel>.CreateChannel(this.binding, this.epa))
					eventId = channel.WriteEntry(entry);
			}
			catch (Exception ex)
			{
				Log.WriteToMachineEventLog(ex.ToString(), MachineEventLogSource.HSSLogService, EventLogEntryType.Error);
			}

			return eventId;
		}

		#endregion
	}
	#endregion
}